/*
 * IOUtil.java
 */

package xq.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;
import org.apache.log4j.Logger;

import static xq.util.ArrayUtil.copyOf;
import static xq.util.LoggerUtil.getClassLogger;

/**
 * 
 * @author ThorntonRP
 */
public class IOUtil {

	private static final Logger logger = getClassLogger();
	
	private static final int DEFAULT_BUFFER_SIZE = 4096;
	private static final byte[] EMPTY_BUFFER = {};

	/*--------------------------------------------------------------------
	 * Character Reading Operations
	 *--------------------------------------------------------------------*/

	public static String readText(URL url) throws IOException {
		return new String(readData(url));
	}

	public static String readText(URL url, int initialBufferSize) throws IOException {
		return new String(readData(url, initialBufferSize));
	}

	public static String readText(File f) throws IOException {
		return new String(readData(f));
	}

	public static String readText(InputStream in) throws IOException {
		return new String(readData(in));
	}

	public static String readText(InputStream in, int initialBufferSize) throws IOException {
		return new String(readData(in, initialBufferSize));
	}

	public static String readText(Reader in) throws IOException {
		return readText(in, DEFAULT_BUFFER_SIZE);
	}

	public static String readText(Reader in, int initialBufferSize) throws IOException {
		try {
			char[] cdata = new char[initialBufferSize];
			char[] buffer = new char[initialBufferSize];
			int size = 0;
			int count;
			do {
				count = in.read(buffer, 0, buffer.length);
				if (count > 0) {
					int newSize = size + count;
					cdata = copyOf(cdata, newSize);
					System.arraycopy(buffer, 0, cdata, size, count);
					size = newSize;
				}
			} while (count != -1);
			cdata = copyOf(cdata, size);
			return new String(cdata);
		} finally {
			try {
				in.close();
			} catch (IOException ex) {
				logger.warn("Failed to close character reader.", ex);
			}
		}
	}

	/*--------------------------------------------------------------------
	 * Binary Reading Operations
	 *--------------------------------------------------------------------*/

	public static byte[] readData(URL url) throws IOException {
		URLConnection conn = url.openConnection();
		int bufferSize = conn.getContentLength();
		if (bufferSize == 0) {
			return EMPTY_BUFFER;
		} else if (bufferSize == -1) {
			bufferSize = DEFAULT_BUFFER_SIZE;
		}
		return readData(url.openStream(), bufferSize);
	}

	public static byte[] readData(URL url, int initialBufferSize) throws IOException {
		return readData(url.openStream(), initialBufferSize);
	}

	public static byte[] readData(File f) throws IOException {
		if (f.length() > Integer.MAX_VALUE) {
			throw new IOException("File is too large to read into a single byte array.");
		} else {
			int bufferSize = (int) f.length();
			return readData(new FileInputStream(f), bufferSize);
		}
	}

	public static byte[] readData(InputStream in) throws IOException {
		return readData(in, DEFAULT_BUFFER_SIZE);
	}

	public static byte[] readData(InputStream in, int initialBufferSize) throws IOException {
		try {
			byte[] data = new byte[initialBufferSize];
			byte[] buffer = new byte[initialBufferSize];
			int size = 0;
			int count;
			do {
				count = in.read(buffer, 0, buffer.length);
				if (count > 0) {
					int newSize = size + count;
					data = copyOf(data, newSize);
					System.arraycopy(buffer, 0, data, size, count);
					size = newSize;
				}
			} while (count != -1);
			data = copyOf(data, size);
			return data;
		} finally {
			try {
				in.close();
			} catch (IOException ex) {
				logger.warn("Failed to close binary input stream.", ex);
			}
		}
	}

	/*--------------------------------------------------------------------
	 * Character Writing Operations
	 *--------------------------------------------------------------------*/

	public static void writeText(File f, String text) throws IOException {
		writeText(new FileWriter(f), text);
	}

	public static void writeText(OutputStream out, String text) throws IOException {
		writeText(new OutputStreamWriter(out), text);
	}

	public static void writeText(Writer out, String text) throws IOException {
		try {
			out.write(text);
			out.flush();
		} finally {
			try {
				out.close();
			} catch (IOException ex) {
				logger.warn("Failed to close character writer.", ex);
			}
		}
	}

	/*--------------------------------------------------------------------
	 * Binary Writing Operations
	 *--------------------------------------------------------------------*/

	public static void writeData(File f, byte[] data) throws IOException {
		writeData(new FileOutputStream(f), data);
	}

	public static void writeData(OutputStream out, byte[] data) throws IOException {
		try {
			out.write(data);
			out.flush();
		} finally {
			try {
				out.close();
			} catch (IOException ex) {
				logger.warn("Failed to close binary output stream.", ex);
			}
		}
	}

	/*--------------------------------------------------------------------
	 * Character Copy Operations
	 *--------------------------------------------------------------------*/

	public static void copyText(Reader in, Writer out) throws IOException {
		copyText(in, out, DEFAULT_BUFFER_SIZE);
	}
	
	public static void copyText(Reader in, Writer out, int bufferSize) throws IOException {
		copyText(in, out, new char[bufferSize]);
	}
	
	public static void copyText(Reader in, Writer out, char[] buffer) throws IOException {
		try {
			int len;
			while ((len = in.read(buffer)) != -1) {
				out.write(buffer, 0, len);
			}
			out.flush();
		} finally {
			in.close();
		}
	}
	
	/*--------------------------------------------------------------------
	 * Binary Copy Operations
	 *--------------------------------------------------------------------*/

	public static void copyData(InputStream in, OutputStream out) throws IOException {
		copyData(in, out, DEFAULT_BUFFER_SIZE);
	}
	
	public static void copyData(InputStream in, OutputStream out, int bufferSize) throws IOException {
		copyData(in, out, new byte[bufferSize]);
	}
	
	public static void copyData(InputStream in, OutputStream out, byte[] buffer) throws IOException {
		int len;
		while ((len = in.read(buffer)) != -1) {
			out.write(buffer, 0, len);
		}
	}
	
	public static void copyData(InputStream[] is, OutputStream out) throws IOException {
		copyData(is, out, DEFAULT_BUFFER_SIZE);
	}
	
	public static void copyData(InputStream[] is, OutputStream out, int bufferSize) throws IOException {
		copyData(is, out, new byte[bufferSize], EMPTY_BUFFER);
	}
	
	public static void copyData(InputStream[] is, OutputStream out, byte[] buffer) throws IOException {
		copyData(is, out, buffer, EMPTY_BUFFER);
	}
	
	public static void copyData(InputStream[] is, OutputStream out, byte[] buffer, byte[] delimiter) throws IOException {
		int index = 0;
		for (InputStream in : is) {
			int len;
			if (index == 0) {
				out.write(delimiter);
			}
			while ((len = in.read(buffer)) != -1) {
				out.write(buffer, 0, len);
			}
			index++;
		}
	}
	
	/*--------------------------------------------------------------------
	 * Miscellaneous Operations
	 *--------------------------------------------------------------------*/

	public static File getLocalFile(URL url) {
		File result = null;
		if ("file".equals(url.getProtocol())) {
			File f = new File(url.getFile());
			if (f.exists()) {
				result = f;
			}
		}
		return result;
	}

	private IOUtil() {
	}
}
